#pragma once

#include "../io.h"

namespace sfc::log {

enum struct Level {
  Trace,  //    [::]
  Debug,  //    [--]
  Info,   //    [**]
  Warn,   //    [??]
  Error,  //    [!!]
  Fatal,  //    [XX]
};

struct Backend {
  void* _self;
  void (*_entry)(void*, Level);
  void (*_write)(void*, Str);

  template <class W>
  Backend(W& imp) noexcept
      : _self{&imp},
        _entry([](void* p, Level x) { static_cast<W*>(p)->entry(x); }),
        _write([](void* p, Str s) { static_cast<W*>(p)->write_str(s); }) {}

  Backend(Backend&&) = default;

  void entry(Level x) {
    _entry(_self, x);
  }

  void write_str(Str s) {
    _write(_self, s);
  }
};

struct Logger {
  Backend _backend;
  Level _level = Level::Info;

  void write(Level level, const auto&... args) {
    if (level < _level) {
      return;
    }
    _backend.entry(level);
    fmt::writeln(_backend, args...);
  }
};

auto logger() -> Logger&;

void trace(const auto&... args) {
  logger().write(Level::Trace, args...);
}

void debug(const auto&... args) {
  logger().write(Level::Debug, args...);
}

void info(const auto&... args) {
  logger().write(Level::Info, args...);
}

void warn(const auto&... args) {
  logger().write(Level::Warn, args...);
}

void error(const auto&... args) {
  logger().write(Level::Error, args...);
}

void fatal(const auto&... args) {
  logger().write(Level::Fatal, args...);
}

}  // namespace sfc::log
